package logger

import (
	"github.com/gin-gonic/gin"
	rotatelogs "github.com/lestrrat-go/file-rotatelogs"
	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
	"io"
	"net"
	"net/http"
	"net/http/httputil"
	"os"
	"runtime"
	"runtime/debug"
	"strings"
	"time"
)

var httpLog *zap.SugaredLogger
var cmdLog *zap.SugaredLogger
var systemWorkPath string

const (
	logTimeFormat   = "2006-01-02 15:04:05.000"
	httpLogDir      = "/logs/http/%Y-%m-%d/%H/"
	cmdLogDir       = "/logs/cmd/%Y-%m-%d/%H/"
	httpLogFileName = "http.log"
	cmdLogFileName  = "cmd.log"
)

func init() {
	//****************************接口日志*******************************************************************
	systemWorkPath, _ = os.Getwd()
	// 设置一些基本日志格式
	encoder := zapcore.NewJSONEncoder(zapcore.EncoderConfig{
		MessageKey:  "msg",
		LevelKey:    "level",
		EncodeLevel: zapcore.CapitalLevelEncoder,
		TimeKey:     "time",
		EncodeTime: func(t time.Time, enc zapcore.PrimitiveArrayEncoder) {
			enc.AppendString(t.Format(logTimeFormat))
		},
		EncodeCaller: zapcore.FullCallerEncoder,
		EncodeDuration: func(d time.Duration, enc zapcore.PrimitiveArrayEncoder) {
			enc.AppendInt64(int64(d) / 1000000)
		},
	})

	// 实现判断日志等级的interface
	infoLevel := zap.LevelEnablerFunc(func(lvl zapcore.Level) bool {
		return lvl == zapcore.InfoLevel
	})

	warnLevel := zap.LevelEnablerFunc(func(lvl zapcore.Level) bool {
		return lvl == zapcore.WarnLevel
	})

	errorLevel := zap.LevelEnablerFunc(func(lvl zapcore.Level) bool {
		return lvl == zapcore.ErrorLevel
	})

	fatelLevel := zap.LevelEnablerFunc(func(lvl zapcore.Level) bool {
		return lvl >= zapcore.FatalLevel
	})

	// 获取 info、error日志文件的io.Writer 抽象 getWriter()
	infoWriter := getWriter("info.log")
	warnWriter := getWriter("warn.log")
	errorWriter := getWriter("error.log")
	fatalWriter := getWriter("fatal.log")
	// 最后创建具体的Logger
	core := zapcore.NewTee(
		zapcore.NewCore(encoder, zapcore.AddSync(infoWriter), infoLevel),
		zapcore.NewCore(encoder, zapcore.AddSync(warnWriter), warnLevel),
		zapcore.NewCore(encoder, zapcore.AddSync(errorWriter), errorLevel),
		zapcore.NewCore(encoder, zapcore.AddSync(fatalWriter), fatelLevel),
	)

	log := zap.New(core, zap.AddCaller())
	zap.ReplaceGlobals(log) // 替换zap包中全局的logger实例
	httpLog = log.Sugar()

	//************************************命令行日志********************
	cmdLevel := zap.LevelEnablerFunc(func(lvl zapcore.Level) bool {
		return lvl >= zapcore.InfoLevel
	})

	cmdWriter := getCmdWriter()
	cmdCore := zapcore.NewTee(
		zapcore.NewCore(encoder, zapcore.AddSync(cmdWriter), cmdLevel),
	)

	cmdLog = zap.New(cmdCore, zap.AddCaller()).Sugar()

	//************************************http日志********************
	httpLevel := zap.LevelEnablerFunc(func(lvl zapcore.Level) bool {
		return lvl >= zapcore.InfoLevel
	})

	httpWriter := getHttpWriter()
	httpCore := zapcore.NewTee(
		zapcore.NewCore(encoder, zapcore.AddSync(httpWriter), httpLevel),
	)

	httpLog = zap.New(httpCore, zap.AddCaller()).Sugar()
}

func getCmdWriter() io.Writer {
	hook, _ := rotatelogs.New(
		systemWorkPath+cmdLogDir+cmdLogFileName,
		rotatelogs.WithRotationTime(time.Hour),
	)

	return hook
}

func getHttpWriter() io.Writer {
	hook, _ := rotatelogs.New(
		systemWorkPath+httpLogDir+httpLogFileName,
		rotatelogs.WithRotationTime(time.Hour),
	)

	return hook
}

func CmdErr(game string, args ...interface{}) {
	if args == nil || args[0] == nil {
		return
	}

	_, local, line, _ := runtime.Caller(1)
	args = append(args, " gameUuid: "+game+" file: "+local+", line: ", line)
	cmdLog.Error(args...)
}

func CmdInfo(game string, args ...interface{}) {
	if args == nil || args[0] == nil {
		return
	}

	_, local, line, _ := runtime.Caller(1)
	args = append(args, " gameUuid: "+game+" file: "+local+", line: ", line)
	cmdLog.Info(args...)
}

func HttpErr(game string, args ...interface{}) {
	if args == nil || args[0] == nil {
		return
	}

	_, local, line, _ := runtime.Caller(1)
	args = append(args, " gameUuid: "+game+" file: "+local+", line: ", line)
	httpLog.Error(args...)
}

func HttpInfo(game string, args ...interface{}) {
	if args == nil || args[0] == nil {
		return
	}

	_, local, line, _ := runtime.Caller(1)
	args = append(args, " gameUuid: "+game+" file: "+local+", line: ", line)
	httpLog.Info(args...)
}

func getWriter(filename string) io.Writer {
	hook, err := rotatelogs.New(
		systemWorkPath+httpLogDir+filename,
		rotatelogs.WithRotationTime(time.Hour),
	)

	if err != nil {
		panic(err)
	}
	return hook
}

func Debug(args ...interface{}) {
	_, local, line, _ := runtime.Caller(1)
	args = append(args, " file: "+local+", line: ", line)
	httpLog.Debug(args...)
}

func Debugf(template string, args ...interface{}) {
	httpLog.Debugf(template, args...)
}

func Info(args ...interface{}) {
	_, local, line, _ := runtime.Caller(1)
	args = append(args, " file: "+local+", line: ", line)
	httpLog.Info(args...)
}

func Infof(template string, args ...interface{}) {
	httpLog.Infof(template, args...)
}

func Warn(args ...interface{}) {
	_, local, line, _ := runtime.Caller(1)
	args = append(args, " file: "+local+", line: ", line)
	httpLog.Warn(args...)
}

func Warnf(template string, args ...interface{}) {
	httpLog.Warnf(template, args...)
}

func Error(args ...interface{}) {
	if args == nil {
		return
	}
	_, local, line, _ := runtime.Caller(1)
	args = append(args, " file: "+local+", line: ", line)
	httpLog.Error(args...)
}

func Errorf(template string, args ...interface{}) {
	if args == nil {
		return
	}

	httpLog.Errorf(template, args...)
}

func DPanic(args ...interface{}) {
	httpLog.DPanic(args...)
}

func DPanicf(template string, args ...interface{}) {
	httpLog.DPanicf(template, args...)
}

func Panic(args ...interface{}) {
	httpLog.Panic(args...)
}

func Panicf(template string, args ...interface{}) {
	httpLog.Panicf(template, args...)
}

func Fatal(args ...interface{}) {
	httpLog.Fatal(args...)
}

func Fatalf(template string, args ...interface{}) {
	httpLog.Fatalf(template, args...)
}

// GinLogger 接收gin框架默认的日志
func GinLogger() gin.HandlerFunc {
	return func(c *gin.Context) {
		start := time.Now()
		query := c.Request.URL.RawQuery
		c.Next()
		httpLog.Info(
			c.Writer.Status(),
			c.Request.URL.Path,
			query, c.ClientIP(),
			c.Request.UserAgent(),
			c.Errors.ByType(gin.ErrorTypePrivate).String(), time.Since(start))
	}

}

// GinRecovery recover掉项目可能出现的panic，并使用zap记录相关日志
func GinRecovery(stack bool) gin.HandlerFunc {
	return func(c *gin.Context) {
		defer func() {
			if err := recover(); err != nil {
				var brokenPipe bool
				if ne, ok := err.(*net.OpError); ok {
					if se, ok := ne.Err.(*os.SyscallError); ok {
						if strings.Contains(strings.ToLower(se.Error()), "broken pipe") || strings.Contains(strings.ToLower(se.Error()), "connection reset by peer") {
							brokenPipe = true
						}
					}
				}

				httpRequest, _ := httputil.DumpRequest(c.Request, false)
				if brokenPipe {
					httpLog.Error(c.Request.URL.Path,
						zap.Any("error", err),
						zap.String("request", string(httpRequest)),
					)
					// If the connection is dead, we can't write a status to it.
					c.Error(err.(error)) // nolint: errcheck
					c.Abort()
					return
				}

				if stack {
					httpLog.Error("[Recovery from panic]",
						zap.Any("error", err),
						zap.String("request", string(httpRequest)),
						zap.String("stack", string(debug.Stack())),
					)
				} else {
					httpLog.Error("[Recovery from panic]",
						zap.Any("error", err),
						zap.String("request", string(httpRequest)),
					)
				}
				c.AbortWithStatus(http.StatusInternalServerError)
			}
		}()
		c.Next()
	}
}
